#import "BWQSDocument.h"
#import "BWDiamondCanvas.h"
#import "BWQueenStitch.h"
#import "BWQueenStitchSet.h"
#import "BWFrenchKnotSet.h"
#import "BWFrenchKnot.h"

// figured experimentally

#define PREVIEW_X 1
#define PREVIEW_Y -1

// correspond to tag numbers in the toolbar sgemeented control
enum {
    kSelectionTool,
    kDrawTool,
    kEraserTool,
    kPrintAreaTool,
    kKnotTool,
    kKnotEraserTool // !!! eraser should be able to nuke both
};


@interface BWQSDocument (ForwardReferences)
- (void) updateUI;
- (void) addStitch: (BWQueenStitch *) stitch
               atX: (int) x
                 y: (int) y;
- (void) addKnotAtX: (int) x
                  y: (int) y;
- (void) moveContentsHorizontally: (int) horizontalShift
                       vertically: (int) verticalShift;
@end // forward references
    

@implementation BWQSDocument

- (id) init
{
    if ([super init] == nil) {
        return (nil);
    }

    stitches = [[BWQueenStitchSet alloc] init];
    previewStitch = [[BWQueenStitchSet  alloc] init];

    frenchKnots = [[BWFrenchKnotSet alloc] init];

    return (self);

} // init


- (void) dealloc
{
    [stitches release];
    [previewStitch release];
    [frenchKnots release];

    [super dealloc];

} // dealloc


- (NSString *) windowNibName
{
    return (@"BWQSDocument");

} // windowNibName


- (void) windowControllerDidLoadNib: (NSWindowController *) aController
{
    [super windowControllerDidLoadNib: aController];

    [canvas setDelegate: self];
    [canvas setStitches: stitches];
    [canvas setFrenchKnots: frenchKnots];
    [canvas setShouldTrack: YES];

    BWQueenStitch *stitch;
    stitch = [BWQueenStitch newStitchAtX: PREVIEW_X
                            y: PREVIEW_Y
                            color1: [NSColor yellowColor]
                            color2: nil
                            direction: kDirection_NorthSouth];
    [previewStitch addStitch: stitch];

    [previewCanvas setShouldTrack: NO];
    [previewCanvas setStitches: previewStitch];
    [previewCanvas setOffsetX: -10.0  y: 3.0];

    [colorWell1 setColor: [NSColor yellowColor]];
    [colorWell2 setColor: [NSColor orangeColor]];

    if (backgroundColor != nil) {
        [canvas setBackgroundColor: backgroundColor];
        [backgroundColorWell setColor: backgroundColor];
    } else {
        [canvas setBackgroundColor: [NSColor whiteColor]];
        [backgroundColorWell setColor: [NSColor whiteColor]];
    }

    [backgroundColor release];
    backgroundColor = nil;
                
    NSImage *image;
    image = [[NSCursor arrowCursor] image];
    [toolbar setImage: image  forSegment: kSelectionTool];
    image = [[NSCursor crosshairCursor] image];
    [toolbar setImage: image  forSegment: kDrawTool];
    image = [NSImage imageNamed: @"eraser-tool"];
    [toolbar setImage: image  forSegment: kEraserTool];
    image = [NSImage imageNamed: @"print-area"];
    [toolbar setImage: image  forSegment: kPrintAreaTool];
    image = [NSImage imageNamed: @"knot-tool"];
    [toolbar setImage: image  forSegment: kKnotTool];
    image = [NSImage imageNamed: @"knot-eraser-tool"];
    [toolbar setImage: image  forSegment: kKnotEraserTool];

    currentTool = kDrawTool;
    [toolbar selectSegmentWithTag: currentTool];
    
} // windowControllerDidLoadNib




- (NSData *) dataOfType: (NSString *) typeName
                  error: (NSError **) error
{
    *error = nil;

    NSMutableData *data = [[NSMutableData alloc] init];

    NSKeyedArchiver *archiver;
    archiver = [[NSKeyedArchiver alloc]
                   initForWritingWithMutableData: data];
    [archiver setOutputFormat: NSPropertyListXMLFormat_v1_0];

    [archiver encodeObject: stitches  forKey: @"stitches"];
    [archiver encodeObject: frenchKnots  forKey: @"frenchKnots"];

    backgroundColor = [canvas backgroundColor];
    [archiver encodeObject: backgroundColor  forKey: @"backgroundColor"];

    [archiver finishEncoding];

    return ([data autorelease]);

} // dataOfType


- (BOOL) readFromData: (NSData *) data
              ofType: (NSString *) typeName
               error: (NSError **) error
{
    *error = nil;

    NSKeyedUnarchiver *archiver;
    archiver = [[NSKeyedUnarchiver alloc]
                   initForReadingWithData: data];
    
    [stitches release];
    stitches = [archiver decodeObjectForKey: @"stitches"];
    [stitches retain];

    [frenchKnots release];
    frenchKnots = [archiver decodeObjectForKey: @"frenchKnots"];
    [frenchKnots retain];

    backgroundColor = [archiver decodeObjectForKey: @"backgroundColor"];
    [backgroundColor retain];

    return (YES);

} // readFromData




// --------------------------------------------------


- (void) removeStitchAtX: (int) x
                       y: (int) y
{
    BWQueenStitch *stitch;
    stitch = [stitches stitchAtX: x  y: y];

    if (stitch != nil) {
        NSUndoManager *undoManager;
        undoManager = [self undoManager];
        
        [[undoManager prepareWithInvocationTarget: self]
            addStitch: stitch];
        
        BWGrid_Square square = { x, y };
        [stitches removeStitch: stitch];
        [canvas setNeedsDisplayInSquare: square];
    }
    
} // removeStitchAtX


- (void) addStitch: (BWQueenStitch *) stitch
{
    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    int x = [stitch x];
    int y = [stitch y];

    [self removeStitchAtX: x
          y: y];

    [[undoManager prepareWithInvocationTarget: self]
        removeStitchAtX: x
        y: y];

    [stitches addStitch: stitch];
    
    if (![undoManager isUndoing]) {
        [undoManager setActionName: @"Add Stitch"];
    }

    BWGrid_Square square = { x, y };
    [canvas setNeedsDisplayInSquare: square];
    
} // addStitchAtX


- (void) addStitchAtX: (int) x
                    y: (int) y
{
    NSColor *color1 = [colorWell1 color];
    NSColor *color2 = nil;
    
    if ([partialStitch state] == NSOnState) {
        color2 = [colorWell2 color];
    }

    int direction = [directionButton state];

    BWQueenStitch *stitch;
    stitch = [BWQueenStitch newStitchAtX: x
                            y: y
                            color1: color1
                            color2: color2
                            direction: direction];
    [self addStitch: stitch];

} // addStitchAtX


- (void) removeKnotAtX: (int) x
                     y: (int) y
{
    BWFrenchKnot *knot;
    knot = [frenchKnots knotAtX: x  y: y];

    if (knot != nil) {
        NSUndoManager *undoManager;
        undoManager = [self undoManager];
        
        [[undoManager prepareWithInvocationTarget: self]
            addKnotAtX: x  y: y];
        
        [frenchKnots removeKnot: knot];
        
        BWGrid_Square square = { x, y };
        [canvas setNeedsDisplayAtIntersection: square];
    }
    
} // removeKnotAtX


- (void) addKnotAtX: (int) x
                  y: (int) y
{
    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    [self removeKnotAtX: x
          y: y];

    [[undoManager prepareWithInvocationTarget: self]
        removeKnotAtX: x
        y: y];

    BWFrenchKnot *knot;
    knot = [BWFrenchKnot newKnotAtX: x
                         y: y];
    [frenchKnots addKnot: knot];
    
    // !!! not quite big enough, or is it?
    BWGrid_Square square = { x, y };
    [canvas setNeedsDisplayAtIntersection: square];
    
} // addKnotAtX




// canvas delegate methods

- (void) canvas: (BWDiamondCanvas *) c  selectionAtX: (int) x
              y: (int) y
           mode: (BWDiamondCanvas_SelectMode) mode
{
    NSUndoManager *undoManager;
    undoManager = [self undoManager];
        
    if (currentTool == kDrawTool) {
        if (mode == kMouseDown) [undoManager beginUndoGrouping];
        [self addStitchAtX: x  y: y];
        if (mode == kMouseUp) [undoManager endUndoGrouping];
    }

    if (currentTool == kEraserTool) {
        if (mode == kMouseDown) [undoManager beginUndoGrouping];
        [self removeStitchAtX: x  y: y];
        if (mode == kMouseUp) [undoManager endUndoGrouping];
    }

    if (currentTool == kSelectionTool) {
        [canvas selectX: x  y: y];
        lastX = x;
        lastY = y;

        BWGrid_Square square = { x, y };
        [canvas setNeedsDisplayInSquare: square];
        
        selectedStitch = [stitches stitchAtX: x  y: y];
        [self updateUI];
        [self updateChangeCount: NSChangeDone];
    }

    if (currentTool == kPrintAreaTool) {
        BWGrid_Square square = { x, y };

        if (mode == kMouseDown) {
            [canvas setShowArea: YES];
            [canvas setAreaAnchor: square];
            [canvas setAreaOpposite: square];
        } else {
            [canvas setAreaOpposite: square];
        }
        [canvas setNeedsDisplay: YES];
    }

    if (currentTool == kKnotTool) {
        if (mode == kMouseDown) [undoManager beginUndoGrouping];
        [self addKnotAtX: x  y: y];
        if (mode == kMouseUp) [undoManager endUndoGrouping];
    }

    if (currentTool == kKnotEraserTool) {
        if (mode == kMouseDown) [undoManager beginUndoGrouping];
        [self removeKnotAtX: x  y: y];
        if (mode == kMouseUp) [undoManager endUndoGrouping];
    }

} // selectionAtX


- (void) canvas: (BWDiamondCanvas *) canvas
      scrolledX: (int) horizontalDelta
              y: (int) verticalDelta
{

    [self moveContentsHorizontally: horizontalDelta
          vertically: verticalDelta];

} // scrolledX
    




- (void) updatePreviewStitch
{
    NSColor *color1 = [colorWell1 color];
    NSColor *color2 = nil;
    
    if ([partialStitch state] == NSOnState) {
        color2 = [colorWell2 color];
    }

    int direction = [directionButton state];

    BWQueenStitch *stitch = [previewStitch anyStitch];
    [stitch setColor1: color1];
    [stitch setColor2: color2];
    [stitch setDirection: direction];

    [previewCanvas setNeedsDisplay: YES];

} // updatePreviewStitch


- (void) updateUI
{
    BWQueenStitch *stitch = selectedStitch;
    
    if (stitch == nil) return;

    [colorWell1 setColor: [stitch color1]];

    if ([stitch color2] == nil) {
        [partialStitch setState: NSOffState];
    } else {
        [colorWell2 setColor: [stitch color2]];
        [partialStitch setState: NSOnState];
    }

    [directionButton setState: [stitch direction]];

    [backgroundColorWell setColor: [canvas backgroundColor]];
    
    [self updatePreviewStitch];

} // updateUI


- (void) swapColorsForStitch: (BWQueenStitch *) stitch
{
    NSUndoManager *undoManager;
    undoManager = [self undoManager];
    
    [[undoManager prepareWithInvocationTarget: self]
        swapColorsForStitch: stitch];

    NSColor *color1 = [stitch color1];
    NSColor *color2 = [stitch color2];
    
    [stitch setColor1: color2];
    if ([partialStitch state] == NSOnState) {
        [stitch setColor2: color1];
    }

    [self updateUI];

    BWGrid_Square square = { [stitch x], [stitch y] };
    [canvas setNeedsDisplayInSquare: square];
    
} // swapColorsForStitch


- (IBAction) swapColors: (id) sender
{
    BWQueenStitch *stitch = selectedStitch;

    if (stitch == nil) {
        NSColor *color1 = [colorWell1 color];
        NSColor *color2 = [colorWell2 color];
        
        [colorWell2 setColor: color1];
        [colorWell1 setColor: color2];

    } else {
        [self swapColorsForStitch: stitch];
    }

    [self updatePreviewStitch];

} // swapColors


- (void) setColor1: (NSColor *) color
         forStitch: (BWQueenStitch *) stitch
{
    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    [[undoManager prepareWithInvocationTarget: self]
        setColor1: [stitch color1]
        forStitch: stitch];
    
    [stitch setColor1: color];

    BWGrid_Square square;
    square.x = [stitch x];    square.y = [stitch y];
    [canvas setNeedsDisplayInSquare: square];

} // setColor1


- (void) setColor2: (NSColor *) color
         forStitch: (BWQueenStitch *) stitch
{
    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    [[undoManager prepareWithInvocationTarget: self]
        setColor2: [stitch color2]
        forStitch: stitch];
    
    [stitch setColor2: color];

    BWGrid_Square square;
    square.x = [stitch x];    square.y = [stitch y];
    [canvas setNeedsDisplayInSquare: square];

} // setColor2


- (IBAction) changeColor1: (id) sender
{
    [self updatePreviewStitch];

    if (selectedStitch == nil) return;

    [self setColor1: [sender color]
          forStitch: selectedStitch];

} // changeColor1


- (IBAction) changeColor2: (id) sender
{
    [self updatePreviewStitch];

    if (selectedStitch == nil) return;

    [self setColor2: [sender color]
          forStitch: selectedStitch];

} // changeColor2


- (void) changeDirectionForStitch: (BWQueenStitch *) stitch
{
    NSUndoManager *undoManager;
    undoManager = [self undoManager];
    
    [[undoManager prepareWithInvocationTarget: self]
        changeDirectionForStitch: stitch];

    BWQueenStitch_Direction direction = [stitch direction];

    [stitch setDirection: BWQueenStitchInvertDirection (direction)];

    BWGrid_Square square = { [stitch x], [stitch y] };
    [canvas setNeedsDisplayInSquare: square];

    [self updateUI];

} // changeDirectionForStitch


- (IBAction) changeDirection: (id) sender
{
    BWQueenStitch *stitch = selectedStitch;

    if (stitch == nil) {
    } else {
        [self changeDirectionForStitch: stitch];
    }

    [self updatePreviewStitch];

} // changeDirection


- (IBAction) changePartialStitch: (id) sender
{
    [self updatePreviewStitch];

    BWQueenStitch *stitch = selectedStitch;

    if ([sender state] == NSOnState) {
        [stitch setColor2: [colorWell2 color]];

    } else if ([sender state] == NSOffState) {
        [stitch setColor2: nil];
    }

    BWGrid_Square square;
    square.x = [stitch x];    square.y = [stitch y];
    [canvas setNeedsDisplayInSquare: square];

    [self updateChangeCount: NSChangeDone];

} // changePartialStitch


- (IBAction) selectTool: (id) sender
{
    int selectedSegment = [toolbar selectedSegment];
    int tag = [[toolbar cell] tagForSegment: selectedSegment];

    currentTool = tag;

    if (currentTool != kSelectionTool) {
        [canvas unselectAll];
        selectedStitch = nil;
    } else {
        [canvas selectX: lastX  y: lastY];
        selectedStitch = [stitches stitchAtX: lastX  y: lastY];
    }

    if (currentTool == kPrintAreaTool) {
        [canvas setShowArea: YES];
    } else {
        [canvas setShowArea: NO];
    }

    if (currentTool == kSelectionTool
        || currentTool == kDrawTool
        || currentTool == kEraserTool
        || currentTool == kPrintAreaTool) {
        [canvas setTrackMode: kTrackDiamonds];

    } else if (currentTool == kKnotTool
        || currentTool == kKnotEraserTool) {
        [canvas setTrackMode: kTrackIntersections];
    }

    [self updateUI];

} // selectTool



// the user has chosen Copy.  Put a pdf on the clipboard of what we
// would have printed

// !!! need to disable copy when there's no selection
// !!! and/or get the view to intelligently figure it out, which might be
// !!! Hard

- (IBAction) copy: (id) sender
{
    // make a place for the PDF data to be stored
    NSMutableData *data;
    data = [NSMutableData dataWithLength: 0];

    BWGrid_Square anchor, opposite;
    anchor = [canvas areaAnchor];
    opposite = [canvas areaOpposite];

    // making a printing view
    BWDiamondCanvas *printView;
    printView = [[BWDiamondCanvas alloc] 
                    initForPrintingWithStitches: stitches
                    backgroundColor: [canvas backgroundColor]
                    areaAnchor: anchor
                    areaOpposite: opposite];

    // run the print operation, accumulating the data into the
    // NSData created above
    NSPrintOperation *printOperation;
    printOperation = [NSPrintOperation 
                         PDFOperationWithView: printView
                         insideRect: [printView bounds]
                         toData: data
                         printInfo: [self printInfo]];
    
    [printOperation runOperation];
    
    // now we have the PDF data.  stick it on the pasteboard
    NSPasteboard *pasteboard;
    pasteboard = [NSPasteboard  generalPasteboard];
    
    [pasteboard declareTypes: [NSArray arrayWithObject: NSPDFPboardType]
               owner: self];

    [pasteboard setData: data
                forType: NSPDFPboardType];

    [printView release];

} // copy


enum {
    kUp,
    kDown,
    kLeft,
    kRight
};


- (void) moveContentsHorizontally: (int) horizontalShift
                       vertically: (int) verticalShift
{
    NSUndoManager *undoManager;
    undoManager = [self undoManager];

    [[undoManager prepareWithInvocationTarget: self]
        moveContentsHorizontally: -horizontalShift
        vertically: -verticalShift];
        
    [stitches shiftStitchesHorizontally: horizontalShift
              vertically: verticalShift];
    [frenchKnots shiftKnotsHorizontally: horizontalShift
                 vertically: verticalShift];

    [canvas setNeedsDisplay: YES];

} // moveContentsHorizontally


- (IBAction) moveStitches: (id) sender
{
    int tag = [sender tag];

    int horizontalShift, verticalShift;

    switch (tag) {
    case kUp:
        horizontalShift = -1;
        verticalShift = -1;
        break;
    case kDown:
        horizontalShift = 1;
        verticalShift = 1;
        break;
    case kLeft:
        horizontalShift = -1;
        verticalShift = 1;
        break;
    case kRight:
        horizontalShift = 1;
        verticalShift = -1;
        break;
    }

    [self moveContentsHorizontally: horizontalShift
          vertically: verticalShift];
    
} // moveStitches


- (void) setBackgroundColor: (NSColor *) color
{
    NSUndoManager *undoManager;
    undoManager = [self undoManager];
    
    [[undoManager prepareWithInvocationTarget: self]
        setBackgroundColor: [canvas backgroundColor]];

    [canvas setBackgroundColor: color];

} // setBackgroundColor


- (IBAction) changeBackgroundColor: (id) sender
{
    [self setBackgroundColor: [sender color]];

} // changeBackgroundColor


- (IBAction) zoom: (id) sender
{
    int tag;
    tag = [[sender selectedItem] tag];

    [canvas setZoom: tag];

} // zoom


@end // BWQSDocument
